/*
 * common.c
 *
 *  Created on: 28 nov. 2013
 *      Author: SAGUET Julien et POCREAU Aymeric
 */

#include "../includes/common.h"
#include "../includes/server.h"

// Créer la socket d'écoute
int create_socket(int ip, int port) {
	int listening_socket;
	struct sockaddr_in servaddr; // socket address struct

	if ((listening_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		exit(EXIT_FAILURE);
	}

	// Pour eviter les erreurs de bind
	int on = 1;
	setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	// Rempli la structure avec des zeros et rempli les champs utiles
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_addr.s_addr = htonl(ip);
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);

	if (bind(listening_socket, (struct sockaddr *) &servaddr, sizeof(servaddr))
			< 0) {
		perror("bind");
		exit(EXIT_FAILURE);
	}

	// Attends la connection avant de l'accepter
	if (listen(listening_socket, BACKLOG) < 0) {
		perror("listen");
		exit(EXIT_FAILURE);
	}
	return listening_socket;
}

// Vérifie si un client est dans la blacklist d'un autre
int checkBlackList(client_t* clientTo, client_t* clientFrom) {
	blackliste tmp = clientTo->blacklist;

	while (tmp != NULL) {
		if (tmp->client == clientFrom) {
			return 0;
		}
		tmp = tmp->next;
	}
	return 1;
}

int readline(int fd, void *str, int max_len) {
	int n, rc;
	char c;
	char* buffer;

	buffer = str;
	for (n = 1; n < max_len; n++) {
		if ((rc = read(fd, &c, 1)) == 1) {
			*buffer++ = c;
			if (c == '\n')
				break;
		} else {
			if (rc == 0) {
				if (n == 1)
					return 0;
				else
					break;
			}
			if (rc == -1) {
				if (errno == EINTR)
					continue;
				else
					return -1;
			}
		}
	}
	*buffer = '\0';
	return n;
}

int sendline(int fd, const void *str, size_t maxlen) {
	int nleft; //caractères restant à écrire
	int nwritten; // caractères déjà écrits

	const char* buffer;

	buffer = str;
	nleft = maxlen;
	while (nleft > 0) {
		if ((nwritten = write(fd, buffer, nleft)) < 0) {
			if (errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}
		nleft -= nwritten;
		buffer += nwritten;
	}
	return maxlen;
}

// Selon la commande, execute l'action adéquate
void checkAction(listeClients liste, client_t* Client, char* buffer,
		groupe_t** listeGroupes, ban_t** listebans) {

	// Affiche le nom des clients connectés
	if (!strcmp(buffer, ".list\n")) {
		getNames(liste, Client->socket);

	// Affiche quee nom des clients connectés au groupe
	} else if (!strcmp(buffer, ".grlist\n")) {
		getGrNames(liste, Client->socket, Client->groupe);

	// Demande d'envoi de fichier
	} else if (!strncmp(buffer, ".request\n", strlen(".request"))) {
		sendRequest(buffer, Client, liste);

	// On envoie le message à une personne en particulier
	} else if (!strncmp(buffer, "@", 1)) {
		sendToName(liste, buffer, Client);

	// Ajouter une personne a sa blackliste
	} else if (!strncmp(buffer, ".bl", 3)) {
		ajouterBlackList(liste, Client, buffer);

	// Créer ou rejoindre un groupe
	} else if (!strncmp(buffer, ".join", 5)) {
		group(listeGroupes, Client, buffer);

	// Envoyer un message a un groupe
	} else if (!strncmp(buffer, "/", 1)) {
		sendtoGroup(listeGroupes, liste, Client, buffer);

	// Expulser un utilisateur d'un groupe
	} else if (!strncmp(buffer, ".gkick", 3)) {
		banGroup(listeGroupes, liste, Client, buffer);

	// Expluser un utilisateur du chat (pour adminisateur)
	} else if (!strncmp(buffer, ".kick", 5)) {
		if (Client->status == 1)
			kick(liste, buffer);

	// Bannir un utilisateur du chat (pour adminisateur)
	} else if (!strncmp(buffer, ".ban", 4)) {
		if (Client->status == 1)
			ban(listebans, liste, buffer);

	// Entrer en mode administrateur
	} else if (!strcmp(buffer, ".admin\n")) {
		if (Client->status == 0)
			administrer(Client);
		else
			sendline(Client->socket, "Vous êtes déjà admin.\n",
					strlen("Vous êtes déjà admin.\n"));

	// Envoyer à tous
	} else {
		sendtoAll(liste, Client, buffer);
	}
}

void kick(listeClients liste, char* buffer) {
	char* pseudo = strstr(buffer, " ") + 1;

	client_t* tmp = liste;
	while (tmp != NULL) {
		if (!strcmp(pseudo, tmp->pseudo)) {
			char* message = "Vous avez été kick du serveur.\n";
			sendline(tmp->socket, message, strlen(message));
			close(tmp->socket);
			deleteFromList(tmp->socket, liste);
		}
		tmp = tmp->next;
	}

}

void ban(ban_t** listebans, listeClients liste, char* buffer) {
	char* pseudo = strstr(buffer, " ") + 1;

	client_t* tmp = liste;
	while (tmp != NULL) {
		if (!strcmp(pseudo, tmp->pseudo)) {
			char* message = "Vous avez été ban du serveur.\n";
			sendline(tmp->socket, message, strlen(message));
			deleteFromList(tmp->socket, liste);
			ban_t *new = malloc(sizeof(ban_t));
			strcpy(new->pseudo, pseudo);
			new->next = NULL;
			if (*listebans == NULL) {
				*listebans = new;
			} else {
				ban_t* temp = *listebans;
				while (temp->next != NULL) {
					temp = temp->next;
				}
				temp->next = new;
			}
		}
		tmp = tmp->next;
	}
}

void administrer(client_t* client) {
	char buffer[MAXBUF];
	char* message1 = "Entrez le mot de passe d'administration\n";
	int socket = client->socket;
	sendline(socket, message1, strlen(message1));
	readline(socket, buffer, MAXBUF);

	// Mot de passe : nianiania
	if (!strcmp(buffer, "niania\n")) {
		client->status = 1;
		char* message2 = "Vous êtes maintenant administrateur\n";
		sendline(socket, message2, strlen(message2));
	}
}

void gestionComptes(int conn_s, char* pseudo) {
	int i = 0;
	FILE* file = NULL;
	char* ret2;
	char ligne[41];
	char pseudo_lu[20];
	char mdp_lu[20];
	int j = 0;

	// Créer ou ouvre le fichier de comptes
	if ((file = fopen("comptes", "a+")) == NULL) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}
	ret2 = fgets(ligne, 41, file);

	// Si le fichier est vide, on propose de créer un compte
	if (ret2 == 0) {
		creationCompte(conn_s, &file, pseudo);

	} else {
		// On lit le fichier tant qu'on ne reçoit pas d'erreur (NULL)
		do {
			i = 0;
			while (ligne[i] != ':') {
				pseudo_lu[i] = ligne[i];
				i++;
			}
			pseudo_lu[i] = '\n';
			pseudo_lu[i + 1] = '\0';
			i++;
			while (ligne[i] != '\n') {
				mdp_lu[j] = ligne[i];
				i++;
				j++;
			}
			mdp_lu[j] = '\n';
			mdp_lu[j + 1] = '\0';
			i = 0;

			// On compare le pseudo avec ceux entrés dans le fichier
			if (!strcmp(pseudo_lu, pseudo)) {

				// Si il est dedans, on propose de se connecter à son compte
				authentification(conn_s, &file, mdp_lu);
				break;
			} else {

				// Sinon, on propose de créer un compte
				creationCompte(conn_s, &file, pseudo);
				break;
			}
			ret2 = fgets(ligne, 41, file);
		} while (ret2 != NULL);
	}
}

void creationCompte(pid_t conn_s, FILE **file, char *pseudo) {
	char buffer[MAXBUF];
	int i = 0;
	pseudo[strlen(pseudo) - 1] = '\0';

	sendline(conn_s, "Souhaitez-vous créer un compte?(oui/non)\n", strlen("Souhaitez-vous créer un compte?(oui/non)\n"));
	readline(conn_s, buffer, MAXBUF);
	while (strcmp(buffer, "oui\n") || strcmp(buffer, "non\n")) {
		if (!strcmp(buffer, "oui\n")) {
			sendline(conn_s, "Entrez un mot de passe\n", strlen("Entrez un mot de passe\n"));
			readline(conn_s, buffer, MAXBUF);
			while (buffer[i] != '\n') {
				i++;
			}
			buffer[i] = '\0';

			// On place le curseur à la fin du fichier
			fseek(*file, 0, SEEK_END);

			// Et on écrit dedans
			fprintf(*file, "%s:%s\n", pseudo, buffer);
			fflush(*file);
			break;
		} else if (!strcmp(buffer, "non\n")) {
			break;
		} else {
			sendline(conn_s, "Mauvaise réponse(oui/non)\n", strlen("Mauvaise réponse(oui/non)\n"));
			readline(conn_s, buffer, MAXBUF);
		}
	}
}

int authentification(pid_t conn_s, FILE **file, char *mdp_lu) {
	char buffer[MAXBUF];

	sendline(conn_s, "Un compte a été trouvé à ce nom. Voulez-vous vous authentifier?(oui/non)\n", strlen("Un compte a été trouvé à ce nom. Voulez-vous vous authentifier?(oui/non)\n"));
	readline(conn_s, buffer, MAXBUF);
	while (strcmp(buffer, "oui\n") || strcmp(buffer, "non\n")) {
		if (!strcmp(buffer, "oui\n")) {
			sendline(conn_s, "Entrez votre mot de passe :\n", strlen("Entrez votre mot de passe :\n"));
			readline(conn_s, buffer, MAXBUF);
			while (strcmp(mdp_lu, buffer)) {
				sendline(conn_s, "Mot de passe incorrect. Entrez votre mot de passe\n",strlen("Mot de passe incorrect. Entrez votre mot de passe\n"));
				readline(conn_s, buffer, MAXBUF);
			}
			sendline(conn_s, "Bienvenue\n", strlen("Bienvenue\n"));
			return 1;
		} else if (!strcmp(buffer, "non\n")) {
			return 0;
		} else {
			sendline(conn_s, "Mauvaise réponse.(oui/non)\n", strlen("Mauvaise réponse.(oui/non)\n"));
			readline(conn_s, buffer, MAXBUF);
		}
	}
	return 0;
}

void banGroup(groupe_t** listeGroupes, listeClients liste, client_t* Client, char* buffer) {
	char* personne = strstr(buffer, " ") + 1;
	client_t* proprio;

	// On récupère le groupe auquel appartient le client qui a fait la commande.
	int id_gr = Client->groupe;

	// On récupère le propriétaire du groupe :
	groupe_t* grtmp = *listeGroupes;
	while (grtmp != NULL) {
		if (grtmp->id == id_gr) {
			proprio = grtmp->master;
			break;
		}
		grtmp = grtmp->next;
	}

	// Si le demandeur est bien le propriétaire
	if (proprio == Client) {
		// Alors on ajoute à la blackliste du groupe, le client spécifié après la commande.

		// On commence par récupérer le pointeur vers le client à expulser
		client_t* clt = liste;
		while (clt != NULL) {
			if (!strcmp(clt->pseudo, personne)) {
				break;
			}
			clt = clt->next;
		}

		// On expulse la personne :
		// 1/ On change son numéro de groupe à 0
		// 2/ On lui notifie son expulsion
		clt->groupe = 0;
		char* message = "Vous avez été renvoyé du groupe.\n";
		sendline(clt->socket, message, strlen(message));
	}
}

void sendtoGroup(groupe_t** listeGroupes, listeClients liste, client_t* Client, char* buffer) {
	char* action = strstr(buffer, " ") + 1;
	int i = 0;
	char canal[20] = "";
	memset(canal, 0, 20);
	char c;
	groupe_t* tmp = NULL;
	client_t* temp = NULL;
	char* de = NULL;
	char* message = NULL;

	for (i = 0; c != ' '; i++) {
		canal[i] = buffer[i + 1];
		c = canal[i];
	}
	canal[i - 1] = '\n';

	tmp = *listeGroupes;

	while (tmp != NULL) {
		if (!strcmp(tmp->name, canal)) {
			tmp = liste;
			while ((temp != NULL)) {
				if (Client->socket != temp->socket && checkBlackList(temp, Client) && Client->groupe == tmp->id) {

					message = malloc(sizeof(char) * (strlen(tmp->name) + strlen(action) + 8));
					message = strcat(message, "Groupe ");
					de = malloc(sizeof(tmp->name));
					de = strcpy(de, tmp->name);
					de[strlen(de) - 1] = 0;
					message = strcat(message, de);
					message = strcat(message, " : ");
					message = strcat(message, action);

					sendline(temp->socket, message, strlen(message));
				}
				temp = temp->next;
			}
		}
		tmp = tmp->next;
	}
}

void group(groupe_t** listeGroupes, client_t* client, char* buffer) {
	char* nomGroupe = strstr(buffer, " ") + 1;

	groupe_t* tmp = NULL;
	char* success = NULL;
	groupe_t* new = NULL;
	groupe_t* tmp2 = NULL;

	while (tmp != NULL) {
		if (!strcmp(nomGroupe, tmp->name)) {
			client->groupe = tmp->id;
			success = malloc(33 + sizeof(nomGroupe));
			success = strcat(success, "Vous avez bien rejoint le groupe ");
			success = strcat(success, nomGroupe);
			sendline(client->socket, success, strlen(success));
			break;
		}
		tmp = tmp->next;
	}

	// Si aucun groupe de ce nom n'a été trouvé
	// On en crée un avec en master le client qui fait l'opération
	if (tmp == NULL) {

		new = malloc(sizeof(groupe_t));

		new->master = client;
		new->next = NULL;
		new->blacklist = NULL;
		strcpy(new->name, nomGroupe);

		tmp = *listeGroupes;

		tmp2 = *listeGroupes;
		if (tmp2 == NULL) {
			new->id = 1;
			*listeGroupes = new;
			client->groupe = new->id;

			char* success = malloc(27);
			success = strcat(success, "Le groupe a bien été créé.\n");
			sendline(client->socket, success, strlen(success));

		} else {
			while (tmp2->next != NULL) {
				tmp2 = tmp2->next;
			}
			new->id = tmp2->id + 1;
			tmp2->next = new;
			client->groupe = new->id;

			success = malloc(27);
			success = strcat(success, "Le groupe a bien été créé.\n");
			sendline(client->socket, success, strlen(success));

		}
	}
}

void ajouterBlackList(listeClients liste, client_t* client, char* buffer) {
	char* Pseudo = NULL;
	client_t* toBL = NULL;
	struct blacklist *new;
	struct blacklist *tmp;

	// On recherche le premier espace après le pseudo auquel envoyer
	Pseudo = strstr(buffer, " ") + 1;

 	toBL = liste;

	while (strcmp(toBL->pseudo, Pseudo)) {
		toBL = toBL->next;
	}

	// On regarde s'il n'est pas déjà dans la blacklist
	new = malloc(sizeof(struct blacklist));
	new->client = toBL;
	new->next = NULL;

	if (client->blacklist == NULL) {
		client->blacklist = new;
	} else {
		tmp = client->blacklist;
		while (tmp->next != NULL) {
			tmp = tmp->next;
		}
		tmp->next = new;
	}
}

void sendtoAll(listeClients liste, client_t* Client, char* buffer) {

//On envoie à tous, SAUF celui qui a envoyé  et à sa blacklist
	client_t* temp = liste;
	while ((temp != NULL)) {
		if (Client->socket != temp->socket && checkBlackList(temp, Client)) {
			sendline(temp->socket, buffer, strlen(buffer));
		}
		temp = temp->next;
	}
}

char* new_client_name(int socket, listeClients liste) {
	char* name = malloc(sizeof(char) * 20);

	// On récupère le pseudo du nouveau client !
	char entrezpseudo[] = "Bienvenue sur le serveur. Entrez un pseudo :\n";
	char autrepseudo[] = "Pseudo déjà utilisé. Réessayez :\n";

	sendline(socket, entrezpseudo, strlen(entrezpseudo));
	readline(socket, name, 20);

	// On regarde s'il n'est pas déjà pris
	while (pseudoExiste(liste, name)) {
		memset(name, 0, 20);
		sendline(socket, autrepseudo, strlen(autrepseudo));
		readline(socket, name, 20);
	}
	return name;
}

void sendRequest(char* message, client_t* Client, listeClients liste) {
	struct sockaddr_in clientaddr;
	int socket_envoie = 0;
	int i = 0;
	char Pseudo[20] = { 0 };
	client_t* temp = NULL;
	int port_recu = 0;

	// On recherche le premier espace avant le pseudo auquel envoyer
	char* pseudo = strstr(message, " ") + 1;

	// On récupère le pseudo
	while (pseudo[i] != '\0') {
		Pseudo[i] = pseudo[i];
		i++;
	}
	fprintf(stdout, "Pseudo cherché : %s", Pseudo);
	// On récupère le socket correspondant
	temp = liste;
	while (temp != NULL) {
		if (!strcmp(temp->pseudo, Pseudo)) {
			socket_envoie = temp->socket;
		}
		temp = temp->next;
	}
	if (socket_envoie == 0) {
		sendline(Client->socket, "Unreachable user\n", strlen("Unreachable user\n"));
	} else {
		fprintf(stdout, "Pseudo trouvé\n");

		// On envoie la requete
		sendline(socket_envoie, ".request\n", strlen(".request\n"));

		// On récupère les informations
		fprintf(stdout, "Requete au client2 envoyée\n");
		port_recu = 0;
		read(socket_envoie, &port_recu, sizeof(int));
		fprintf(stdout, "%d\n", port_recu);

		// Maintenant qu'on a le port du client 2
		// On crée une struct sockaddr_in
		// Que l'on remplit avec l'ip du client 2
		memset(&clientaddr, 0, sizeof(clientaddr));
		clientaddr.sin_addr.s_addr = Client->structure.sin_addr.s_addr;
		clientaddr.sin_family = AF_INET;
		clientaddr.sin_port = port_recu;

		// Puis que l'on envoie au client 1
		fprintf(stdout, "ip : %s\n", inet_ntoa(Client->structure.sin_addr));
		fprintf(stdout, "port dans structure : %d\n", clientaddr.sin_port);

		// On l'envoie au client demandeur
		write(Client->socket, &clientaddr, sizeof(struct sockaddr_in));
	}
}

void getNames(listeClients liste, int socket) {
	client_t* temp = liste;

	while (temp != NULL) {
		sendline(socket, temp->pseudo, strlen(temp->pseudo));
		temp = temp->next;
	}

}

void getGrNames(listeClients liste, int socket, int idGroup) {
	client_t* temp = liste;

	while (temp != NULL) {
		if (temp->groupe == idGroup) {
			sendline(socket, temp->pseudo, strlen(temp->pseudo));
			temp = temp->next;
		}
	}

}

int sendToName(listeClients liste, char* message, client_t* from) {

	char Pseudo[20];
	int i = 0;
	client_t* temp  = NULL;
	char* de = NULL;

	// On recherche le premier espace après le pseudo auquel envoyer
	char* vraiMessage = strstr(message, " ") + 1;
	while (*(message + i) != ' ') {
		Pseudo[i] = *(message + (i + 1));
		i++;
	}
	Pseudo[i - 1] = '\n';

	// On recherche le socket sur lequel envoyer
	temp = liste;
	while (temp != NULL) {
		if (!strcmp(temp->pseudo, Pseudo) && checkBlackList(temp, from)) {
			char* message = malloc(sizeof(char) * (strlen(Pseudo) + strlen(vraiMessage) + 3));
			de = malloc(sizeof(from->pseudo));
			de = strcpy(de, from->pseudo);
			de[strlen(de) - 1] = 0;
			message = strcat(message, de);
			message = strcat(message, " : ");
			message = strcat(message, vraiMessage);
			sendline(temp->socket, message, strlen(message));
			return 1;
		}
		temp = temp->next;
	}
	return 0;
}

int pseudoExiste(listeClients liste, const char *Pseudo) {
	client_t* temp = liste;

	while (temp != NULL) {
		if (!strcmp(temp->pseudo, Pseudo))
			return 1;
		temp = temp->next;
	}
	return 0;
}

listeClients ajouterClient(listeClients liste, int socket, char* Pseudo,
		struct sockaddr_in client) {

	client_t *new = malloc(sizeof(client_t));
	client_t* temp = NULL;
	char* newusr = NULL;

	new->socket = socket;
	Pseudo = strcpy(new->pseudo, Pseudo);
	new->next = NULL;
	new->groupe = 0;
	new->status = 0;
	new->structure = client;
	new->blacklist = NULL;

	if (liste == NULL) {
		return new;
	} else {
		temp = liste;

		newusr = malloc(42 * sizeof(char));
		newusr = strcpy(newusr, "Nouvel utilisateur : ");
		newusr = strcat(newusr, Pseudo);

		while (temp->next != NULL) {
			temp = temp->next;
		}
		temp->next = new;

		sendtoAll(liste, new, newusr);

		return liste;
	}
}

listeClients deleteFromList(int Socket, listeClients liste) {
	client_t* temp = liste;
	char* usrleave = malloc(34 * sizeof(char));
	client_t* delete;

	usrleave = strcpy(usrleave, "Deconnexion : ");
	if (temp->socket == Socket) {

		usrleave = strcat(usrleave, temp->pseudo);

		sendtoAll(liste, temp, usrleave);

		delete = temp->next;
		free(temp);

		return delete;
	} else {
		while (temp->next != NULL) {

			if (temp->next->socket == Socket) {
				usrleave = strcat(usrleave, temp->next->pseudo);

				sendtoAll(liste, temp->next, usrleave);

				temp->next = temp->next->next;

				free(temp->next);
				return liste;
			}
			temp = temp->next;
		}
	}
	fprintf(stdout, "rien trouve \n");
	return liste;
}
